
#include <stdio.h>
#include <math.h>
#include "decompress.h"

#ifndef ENIGMA_SPACE
#define ENIGMA_SPACE
namespace enigma
{

char processed_bits = 0;
char packet_length = 0;
char mode = 0;
char output_repeatcount = 0;
//char remaining_bits = 0x10;
char bits_remaining_postfunc = 0;
char bitmask = 0;
unsigned short input_stream = 0;
unsigned short incrementing_value = 0;
unsigned short common_value = 0;
unsigned short addvalue = 0;
unsigned short outvalue = 0;
int loopcount;

int infile;
int outfile;
int infilelength;
int offset = 0;
unsigned char *pointer = 0;
unsigned char *outpointer = 0;
unsigned char *paddingpointer = 0;
int outoffset = 0;
bool done = false;
int result;

int remaining_bits = 8;
unsigned char input_buffer;
bool init = true;

}
#endif


//buffer version of kosinski decompression
int get_bit(byte *source, int &srcindex, unsigned short int *field, int *bit_cnt)
{
   unsigned short int bit = *field & 1;

   *field >>= 1;

   (*bit_cnt)--;

   if (*bit_cnt < 0)
   {
      *field = source[srcindex]+((source[srcindex+1])<<8);      
      srcindex=srcindex+2;
      *bit_cnt = 15;
   }

  return bit;
}

/*
Kos_Decomp:
		subq.l	#2,sp	; make space for two bytes on the stack
		move.b	(a0)+,1(sp)
		move.b	(a0)+,(sp)
		move.w	(sp),d5	; copy first description field
		moveq	#$F,d4	; 16 bits in a byte

Kos_Decomp_Loop:
		lsr.w	#1,d5	; bit which is shifted out goes into C flag
		move	sr,d6
		dbf	d4,Kos_Decomp_ChkBit
		move.b	(a0)+,1(sp)
		move.b	(a0)+,(sp)
		move.w	(sp),d5	; get next description field if needed
		moveq	#$F,d4	; reset bit counter

Kos_Decomp_ChkBit:
		move	d6,ccr	; was the bit set?
		bcc.s	Kos_Decomp_RLE	; if not, branch (C flag clear means bit was clear)
		move.b	(a0)+,(a1)+	; otherwise, copy byte as-is
		bra.s	Kos_Decomp_Loop
; ---------------------------------------------------------------------------

Kos_Decomp_RLE:
		moveq	#0,d3
		lsr.w	#1,d5	; get next bit
		move	sr,d6
		dbf	d4,Kos_Decomp_ChkBit2
		move.b	(a0)+,1(sp)
		move.b	(a0)+,(sp)
		move.w	(sp),d5
		moveq	#$F,d4

Kos_Decomp_ChkBit2:
		move	d6,ccr	; was the bit set?
		bcs.s	Kos_Decomp_SeparateRLE	; if it was, branch
		lsr.w	#1,d5	; bit which is shifted out goes into X flag
		dbf	d4,+
		move.b	(a0)+,1(sp)
		move.b	(a0)+,(sp)
		move.w	(sp),d5
		moveq	#$F,d4
+
		roxl.w	#1,d3	; get high repeat count bit (shift X flag in)
		lsr.w	#1,d5
		dbf	d4,+
		move.b	(a0)+,1(sp)
		move.b	(a0)+,(sp)
		move.w	(sp),d5
		moveq	#$F,d4
+
		roxl.w	#1,d3	; get low repeat count bit
		addq.w	#1,d3	; increment repeat count
		moveq	#-1,d2
		move.b	(a0)+,d2	; calculate offset
		bra.s	Kos_Decomp_RLELoop
; ---------------------------------------------------------------------------

Kos_Decomp_SeparateRLE:
		move.b	(a0)+,d0	; get first byte
		move.b	(a0)+,d1	; get second byte
		moveq	#-1,d2
		move.b	d1,d2
		lsl.w	#5,d2
		move.b	d0,d2	; calculate offset
		andi.w	#7,d1	; does a third byte need to be read?
		beq.s	Kos_Decomp_SeparateRLE2	; if it does, branch
		move.b	d1,d3	; copy repeat count
		addq.w	#1,d3	; and increment it

Kos_Decomp_RLELoop:
		move.b	(a1,d2.w),d0
		move.b	d0,(a1)+	; copy appropriate byte
		dbf	d3,Kos_Decomp_RLELoop	; and repeat the copying
		bra.s	Kos_Decomp_Loop
; ---------------------------------------------------------------------------

Kos_Decomp_SeparateRLE2:
		move.b	(a0)+,d1
		beq.s	Kos_Decomp_Done	; 0 indicates end of compressed data
		cmpi.b	#1,d1
		beq.w	Kos_Decomp_Loop	; 1 indicates a new description needs to be read
		move.b	d1,d3	; otherwise, copy repeat count
		bra.s	Kos_Decomp_RLELoop
; ---------------------------------------------------------------------------

Kos_Decomp_Done:
		addq.l	#2,sp	; restore stack pointer to original state
		rts
; End of function Kos_Decomp
*/

void KosDec(byte *source, byte *destination, bool Moduled)
{


  byte *buffer=destination;
  int buf_pos = 0;

  unsigned short field;
  int bit_cnt;

  unsigned char high, low, buf;

  int count;
  signed short offset;

   int srcindex=0;

  field = source[srcindex]+((source[srcindex+1])<<8);
  printf("\n\nfield: %i\n", field);
  srcindex=srcindex+2;
  bit_cnt = 15;

  while(1)
    {
      if (get_bit(source, srcindex, &field, &bit_cnt))       //if 1
        {
          //fread(&buf, sizeof(buf), 1, file);
          buf=source[srcindex++];
          buffer[buf_pos++] = buf;
        
          continue;
        }
      else
        {
          if (get_bit(source, srcindex, &field, &bit_cnt))      //if 01 seperate rle
          {
              //seperate rle indicated by 2 separate formats...
              //[LLLL LLLL] [HHHH HCCC]   OR
              //[LLLL LLLL] [HHHH H000] [CCCC CCCC]    this format indicated by the 000 in the 2nd byte...
              
              //kens does it like this...
              
              //STEP 1) CALCULATE OFFSET
              low=source[srcindex++];    //get the first two bytes
              high=source[srcindex++];   //

              offset = (0xFF00 | high) << 5;      //offset is then calculated here
              offset = (offset & 0xFF00) | low;   //.....


              //STEP 2) DETERMINE FORMAT FOR C BITS (2 byte/3 byte?)
              high &= 0x07;                       //only the last three bits in byte two determine the 3 byte format

              if (high)                           //if this is nonzero then its the 2 byte format
              {
                  count = high + 1;               //count is then just these 3 bits
              }
              else                                //if this is zero then its the 3 byte format...
              {
                  buf=source[srcindex++];         //so read the 3rd byte
                  
                  if (buf == 1) continue;         //case when you discard current description then read the next
                  count = buf;                     
                  if (count == 0) break;          //case when end of stream
              }
          }
          else                                //if 00
          {
              count = (get_bit(source, srcindex, &field, &bit_cnt) << 1) |
                       get_bit(source, srcindex, &field, &bit_cnt);
              count++;

              buf=source[srcindex++];
              offset = 0xFF00 | buf;
          }
      }

      if ((buf_pos + offset) < 0)
      {
          printf("Invalid offset found, terminating.\n");
          
          return;
      }

      while (count >= 0)
      {
          buf = buffer[buf_pos + offset];
          buffer[buf_pos++] = buf;

          count--;
      }
    }
    //printf("DECOMPRESSED KOS: \n");
    for (int lp=0; lp<(buf_pos+offset)/2; lp++)
    {
        if (lp%256 == 0){/*printf("decompressed tile %02X:\n",lp/256);*/}
        //printf("%04X ", (buffer[(lp*2)]<<8)+(buffer[lp*2+1]));
        
        if (lp%16==15){/*printf("\n");*/}
        if (lp%256==255){/*printf("\n");*/}
     }
}

/////////////////////////////////////////////////////////////////////////////////////////
//Program entry point
long EniDec(byte *source, byte *destination)
{

    
    using namespace enigma;
// I prefered to initialize the following variables before starting the algorithm.


//	remaining_bits = 0x10;
	bits_remaining_postfunc = 0;
	bitmask = 0;
	input_stream = 0;
	incrementing_value = 0;
	common_value = 0;
	addvalue = 0;
	outvalue = 0;
	offset = 0;
	outoffset = 0;
	done = false;

	remaining_bits = 8;
	init = true;

	//pointer = (unsigned char *)calloc(infilelength, 0x01);
	pointer = source;
    outpointer = destination; //(byte *)calloc(0x100000, 0x01);
	//paddingpointer = (unsigned char *)calloc(0x1000, 0x01);

/////////////////////////////////////////////////////////////////////////////////////////
//Entry point
	packet_length = getbits(8, pointer, &offset);
	bitmask = getbits(8, pointer, &offset);
	incrementing_value = getbits(8, pointer, &offset) << 8;
	incrementing_value |= getbits(8, pointer, &offset);
	common_value = getbits(8, pointer, &offset) << 8;
	common_value |= getbits(8, pointer, &offset);
    //printf("packet_length: %i\n", packet_length);
    //printf("bitmask: %i\n", bitmask);
    //printf("incrementing_value: %i\n", incrementing_value);
    //printf("common_value: %i\n", common_value);


/////////////////////////////////////////////////////////////////////////////////////////
//Main algorithm
	for(;!done;)
	{
		if((getbits(1, pointer, &offset)) == 1)
		{
			mode = getbits(2, pointer, &offset);
			switch(mode)
			{
			case 0:
			case 1:
			case 2:
				{
					output_repeatcount = getbits(4, pointer, &offset);
					getvalue();
					for(;output_repeatcount >= 0; output_repeatcount--)
					{
						*(outpointer + outoffset++) = ((outvalue >> 8) & 0x00FF);
						*(outpointer + outoffset++) = (outvalue & 0x00FF);

						switch(mode)
						{
						case 0:
							break;
						case 1:
							outvalue++;
							break;
						case 2:
							outvalue--;
							break;
						}
					}
					break;
				}
			case 3:
				{
					output_repeatcount = getbits(4, pointer, &offset);
					if(output_repeatcount != 0x0F)
					{
						for(;output_repeatcount >= 0; output_repeatcount--)
						{
							getvalue();
							*(outpointer + outoffset++) = ((outvalue >> 8) & 0x00FF);
							*(outpointer + outoffset++) = (outvalue & 0x00FF);
						}
					}
					else
					{
						done = true;
					}
					break;
				}
			}
		}
		else
		{
			if((getbits(1, pointer, &offset)) == 0)
			{
				output_repeatcount = getbits(4, pointer, &offset);
				for(; output_repeatcount >= 0; output_repeatcount--)
				{
					*(outpointer + outoffset++) = ((incrementing_value >> 8) & 0x00FF);
					*(outpointer + outoffset++) = (incrementing_value & 0x00FF);
					incrementing_value++;
				}
			}
			else
			{
				output_repeatcount = getbits(4, pointer, &offset);
				for(; output_repeatcount >= 0; output_repeatcount--)
				{
					*(outpointer + outoffset++) = ((common_value >> 8) & 0x00FF);
					*(outpointer + outoffset++) = (common_value & 0x00FF);
				}
			}
		}
	}

/////////////////////////////////////////////////////////////////////////////////////////

//	//printf("Done!\n\n");
//	//printf("Archive in %s successfully extracted to file %s.\n\n", argv[1], argv[2]);
//	//printf("Compressed file size:\t\t0x%X\t\t%i\n", (offset - originaloffset), (offset - originaloffset));
//	//printf("Uncompressed file size:\t\t0x%X\t\t%i\n", _tell(outfile), _tell(outfile));
//	//printf("Location in file:\t\t0x%X - 0x%X\n", originaloffset, offset);
//	//printf("Compression rate:\t\t%.2f%%\n\n", (100 - (((float)(offset - originaloffset) / (float)(_tell(outfile))) * 100)));

	//free(pointer);
	//free(outpointer);
	//free(paddingpointer);
	return 0;
}

/////////////////////////////////////////////////////////////////////////////////////////
//Getvalue function		- This function extracts a 16-bit value from the compressed data.
bool getvalue(void)
{
     using namespace enigma;
	addvalue = 0;
	for(loopcount = 0; loopcount < 5; loopcount++)
	{
		if(((bitmask >> (4 - loopcount)) & 0x01) != 0)
		{
			addvalue |= (getbits(1, pointer, &offset) << (0xF - loopcount));
		}
	}

	if(packet_length > 8)
	{
		outvalue = getbits(packet_length - 8, pointer, &offset) << 8;
		outvalue |= getbits(8, pointer, &offset);
	}
	else
	{
		outvalue = getbits(packet_length, pointer, &offset);
	}

	outvalue &= (0xFFFF ^ (0xFFFF << packet_length));
	outvalue += addvalue;
	return true;
}


unsigned char getbits(int number, byte* input, int *offset)
{   
    using namespace enigma;
	if (init) { input_buffer = *(input + (*offset)++); init = false; }
	unsigned char value = 0;

	if(number > remaining_bits)
	{
		value = input_buffer >> (8 - number);
		input_buffer = *(input + (*offset)++);
		value |= ((input_buffer & (0xFF << (8 - number + remaining_bits))) >> (8 - number + remaining_bits));
		input_buffer <<= (number - remaining_bits);
		remaining_bits = 8 - (number - remaining_bits);
	}
	else
	{
		value = ((input_buffer & (0xFF << (8 - number))) >> (8 - number));
		remaining_bits -= number;
		input_buffer <<= number;
	}

	return value;
}


long NemDec(byte *source, byte *destination)
{
     
     //printf("NEMDEC\n");
     //printf("destination: %i\n", destination);
// In the following code, I removed the declaration of the Pointer variable as it is now passed
// as parameters. I also removed all console I/O operation as the function does not need to ask
// for filenames and for a pointer, as they are passed as parameters.

//set input and output modes to hex
//	cin.setf(ios::hex);

//misc variables and arrays
	int openrom = 0, result = 0, out_loc = 0, loopcount = 0, file = 0, romsize = 0;
	long pointer = (long)source; //place in the rom to load from (EDIT: 0 IN OUR CASE) */
	unsigned short rtiles; //remaining tiles to decompress
	bool alt_out = false; //flag to change between the two different output modes
	//unsigned long tiles[0x8000]; //output array
	unsigned long* tiles=(unsigned long*)destination;
    
    
    //char romfilename[260]; //name of rom */
	//char outfilename[260]; //name of output file */

// We set up romfilename and outfilename according to the passed parameters
	//strcpy(romfilename, SrcFile);
	//strcpy(outfilename, DstFile);

//prompts for name of rom and reads it into memory
/*	cout << "Enter name of rom:\t\t\t"; */
/*	cin >> romfilename; */
	//openrom = _open( romfilename, _S_IWRITE, _O_BINARY );
	//if (openrom < 0)
	//{
/*		cout << "\n\nInvalid rom filename!"; */
	//	return -1;
	//}
	//result = _setmode(openrom, _O_BINARY);
	//romsize = _filelength(openrom);
	//char* rompointer = (char *)calloc( romsize, 0x01 );
	//result = _read(openrom, rompointer, romsize);

//prompts for name of output file and pointer in rom
/*	cout << "Enter name of output file:\t\t"; */
/*	cin >> outfilename; */
/*	cout << "Enter location of archive in rom:\t"; */
/*	cin >> pointer; */

//allocates block of memory for decompression buffer
   byte *bufferpointer = new byte[0x400];
   byte *rompointer=0;
   //byte *bufferpointer=destination;
   
// There starts the original code by Nemesis
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//entry point into algorithm
	rtiles = (*(pointer++ + rompointer) * 0x100); //reads in the number of tiles in this block
	rtiles += (*(pointer++ + rompointer) & 0xFF);
	alt_out = (rtiles & 0x8000) / 0x8000; //ses the output mode based on the value of the first bit
	rtiles = 0x7FFF & rtiles; //truncates the first bit as it is no longer significant
	
    //printf("rtiles: %i\n", rtiles);
    result = stage_1(&pointer, rompointer, bufferpointer); //calls the header decompression routine
	if(result == -1)
	{
		return -1;
	}
	stage_2(&pointer, rompointer, bufferpointer, &tiles[0], rtiles, alt_out, &out_loc); //calls the main decompression routine


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Additional filehanding to read decompressed tiles from memory into file
	//file = _creat(outfilename, _S_IWRITE);
	//result = _setmode(file, _O_BINARY);
	for(loopcount = 0; loopcount < out_loc; loopcount++)
	{
		tiles[loopcount] = ((tiles[loopcount] & 0xFFFF0000) >> 16) + 
						   ((tiles[loopcount] & 0x0000FFFF) << 16);//byteswaps the output before writing to the output file
		//result = _write(file, (&tiles[loopcount]) , 4);
	}
        delete bufferpointer;
	//result = _close(file);
	//_close(openrom);*/
	return 0;
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//First stage decompression algorithm
int stage_1 (long *pointer, byte *rompointer, byte *bufferpointer)
{
    //printf("stage_1\n");
	unsigned short in_val; //storage for value coming from rom
	unsigned short out_val; //storage for output value to decompression buffer
	unsigned short num_times; //number of times the calculated output value is written to the buffer
	unsigned short offset; //stores buffer offset to write to
	//main loop. Header is terminated by the value of 0xFF in the rom
	for (in_val = (*((*pointer)++ + rompointer) & 0xFF); in_val != 0xFF; in_val = (*((*pointer)++ + rompointer) & 0xFF))
	{
		if(in_val > 0x7F) //if most significant bit is set, store the last 4 bits and discard the rest
		{
			out_val = in_val; //last four bits set here are not changed until it enters here again
			in_val = (*((*pointer)++ + rompointer) & 0xFF); //read another value from the rom
		}
		num_times = 0x08 - (in_val & 0x0F); //the last 4 bits here determine the number of times the value is written to the buffer. Never greater than 8
		in_val = ((in_val & 0xF0) / 0x10) + ((in_val & 0x0F) * 0x10); //nibble swap the value from the rom
		out_val = (out_val & 0x0F) + (in_val * 0x10); //multiply the input value by 0x10 and place it in front of the last 4 bits in the output value
		offset = (*((*pointer)++ + rompointer) & 0xFF) * pow(2, (num_times + 1)); //read another value from the rom and use it to determine the offset
		if(offset >= 0x200)
		{
			return -1;
		}

		num_times = pow(2, num_times); //finish setting the number of times the value is written to the buffer
		for(; num_times != 0; num_times--,offset += 2) //loop for writing the values to the buffer
		{
			*(bufferpointer + offset + 1) = (out_val & 0x00FF);
			*(bufferpointer + offset) = ((out_val & 0xFF00) / 0x100); //wriiting the values to the buffer
		}
	}
	return 0;
}


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Second stage decompression algorithm
void stage_2 (long *pointer, byte *rompointer, byte *bufferpointer, unsigned long *tiles, short rtiles, bool alt_out, int *out_loc)
{
     //printf("Stage 2\n");
	unsigned short rnibbles; //remaining nibbles in this line
	unsigned long out_val; //output value
	unsigned short rom_mod = 0x10; //This one is complex. Used in maths as a power of 2, but also controls when 
									//new values are read from the rom. Just read the algorithm.
	unsigned long alt_out_val = 0; //If the alternate output method is used by this archive, each line is XOR'd with
								   //the line before it prior to writing to the output.
	unsigned short rlines; //remaining lines to decompress in this tile
	unsigned short mode_val; //There are two different things it can do in the main loop. This holds the value to switch between them
	unsigned short bufferoffset; //the buffer offset to read from next
	unsigned short next_out = 0; //the next nibble to be added to the output.
	short num_times = 0; //the number of times the value is repeated in the output
	unsigned short rom_val = (*((*pointer)++ + rompointer) * 0x100); //value from the rom
	rom_val += (*((*pointer)++ + rompointer) & 0xFF);
	////printf("rom_val: %i\n", rom_val);
	////printf("pointer: %i, *pointer=%i\n",(*pointer-1));
	//printf("tiles data=%i\n", tiles);
	for(;rtiles > 0;rtiles--) //main loop for tiles
	{
		for (rlines = 8; rlines != 0; rlines--) //loop for lines in the tile
		{
			for (out_val = 0, rnibbles = 8; rnibbles != 0; rnibbles--) //loop for nibbles in the line
			{
				num_times--; //decrements number of times to write current value to output
				if (num_times == -1) //if -1, calculate new value
				{
					mode_val = rom_val / pow(2, (rom_mod - 8)); //determines which set of commands to perform
					if ((mode_val & 0x00FF) >= 0xFC) //determining value is 0xFC or greater
					{
						if (rom_mod < 0x0F) //determines whether or not to read another value from the rom
						{
							rom_mod += 8; //and adds 8 to rom_mod if it does
							rom_val = (rom_val * 0x100) + (*((*pointer)++ + rompointer) & 0xFF);
						}
						rom_mod -= 0x0D; //subtracts 0x0D from rom_mod
						num_times = ((short)(rom_val / pow(2, rom_mod)) & 0x70) / 0x10; //calculates number of times to write value to output
						next_out = (short)(rom_val / pow(2, rom_mod)) & 0x0F; //calculates next output value
					}
					else
					{
						bufferoffset = (mode_val & 0xFF) * 2; //sets buffer offset
						rom_mod -= *(bufferpointer + bufferoffset); //reads a buffer value, then subtracts it from rom_mod. Never greater than 0x7
						num_times = (*(bufferpointer + bufferoffset + 1) & 0xF0) / 0x10; //set number of times based on a buffer value. Never greater than 0x7
						next_out = *(bufferpointer + bufferoffset + 1) & 0x0F; //gets next output value from buffer
					}
					if (rom_mod < 9) //again, potentially load a value from the rom
					{
						rom_mod += 8; //and add 8 to rom_mod
						rom_val = (rom_val * 0x100) + (*((*pointer)++ + rompointer) & 0xFF);
					}
				}
				out_val = out_val * 0x10 + next_out; //adds next value to output
			}
			if (alt_out == false)
			{
				*(tiles + (*out_loc)++) = out_val; //writes next line to output array
			}
			else
			{
				alt_out_val = alt_out_val ^ out_val; //Alternate output method. Only bits set are those different to the last line
				*(tiles + (*out_loc)++) = alt_out_val;
			}
		}
	}
		//printf("Decompression done\n");
}


////////

